#include "matrixmodel.h"
#include "matrix.h"

#include <QSharedPointer>
class MatrixModelPrivate
{
public:
    MatrixModelPrivate(MatrixModel* q);
    ~MatrixModelPrivate();

    void populate();

    MatrixModel* q_ptr;
    QSharedPointer<MatrixBase> matrix;
    bool autoRelease = true;

    QVector<double> horizontalHeaderData;
    QVector<double> verticalHeaderData;
};

MatrixModelPrivate::MatrixModelPrivate(MatrixModel *q) : q_ptr(q)
{

}

MatrixModelPrivate::~MatrixModelPrivate()
{
#ifdef NIP
    if (matrix != nullptr && autoRelease) {
        delete matrix;
        matrix = nullptr;
    }
#endif
}

void MatrixModelPrivate::populate()
{

}

MatrixModel::MatrixModel(QObject* parent) : QAbstractTableModel(parent)
{
    d_ptr = new MatrixModelPrivate(this);
}

MatrixModel::~MatrixModel()
{
    delete d_ptr;
}

void MatrixModel::setAutoRelease(bool release)
{
    if (d_ptr->autoRelease != release)
        d_ptr->autoRelease = release;
}

bool MatrixModel::isAutoRelease() const
{
    return d_ptr->autoRelease;
}

void MatrixModel::setMatrix(const QSharedPointer<MatrixBase>& matrix)
{
    if (d_ptr->matrix.data() != matrix.data()) {
        d_ptr->matrix = matrix;
        connect(d_ptr->matrix.data(), SIGNAL(valueChanged(int,int,QVariant,QVariant)), this, SLOT(onMatrixValueChanged(int,int,QVariant,QVariant)));
        emit layoutChanged();
    }
}

QSharedPointer<MatrixBase> MatrixModel::matrix() const
{
    return d_ptr->matrix;
}

int MatrixModel::rowCount(const QModelIndex & parent) const
{
    Q_UNUSED(parent)
    if (d_ptr->matrix)
        return d_ptr->matrix->rows();
    return 0;
}

int MatrixModel::columnCount(const QModelIndex & parent) const
{
    Q_UNUSED(parent)
    if (d_ptr->matrix)
        return d_ptr->matrix->columns();
    return 0;
}

template <int type, class T>
static inline QVariant getMatrixValue(MatrixBase* matrix, int row, int col, int role)
{
    Q_UNUSED(role)
    if (matrix && matrix->type() == type) {
        T* convMatrix = dynamic_cast<T*>(matrix);
        return convMatrix->value(row, col);
    }
    return QVariant();
}

template <>
static inline QVariant getMatrixValue<MatrixBase::ComplexFloat, ComplexFloatMatrix>
(MatrixBase* matrix, int row, int col, int role)
{
    if (matrix && matrix->type() == MatrixBase::ComplexFloat) {
        ComplexFloatMatrix* convMatrix = dynamic_cast<ComplexFloatMatrix*>(matrix);
        const ComplexFloatType& cell = convMatrix->value(row, col);

        if (role == Qt::DisplayRole)
            return QString::asprintf("%f%+fi", cell.real(), cell.imag());

        if (role == Qt::EditRole)
            return QVariant::fromValue<ComplexFloatType>(cell);
    }
    return QVariant();
}

template <>
static inline QVariant getMatrixValue<MatrixBase::ComplexDouble, ComplexDoubleMatrix>
(MatrixBase* matrix, int row, int col, int role)
{
    if (matrix && matrix->type() == MatrixBase::ComplexDouble) {
        ComplexDoubleMatrix* convMatrix = dynamic_cast<ComplexDoubleMatrix*>(matrix);
        const ComplexDoubleType& cell = convMatrix->value(row, col);

        if (role == Qt::DisplayRole)
            return QString::asprintf("%f%+fi", cell.real(), cell.imag());

        if (role == Qt::EditRole)
            return QVariant::fromValue<ComplexDoubleType>(cell);
    }
    return QVariant();
}

static inline QVariant getMatrixData(MatrixBase* matrix, const QModelIndex& index, int role)
{
    switch (matrix->type()) {
    case MatrixBase::Int:
        return getMatrixValue<MatrixBase::Int, IntMatrix>(matrix, index.row(), index.column(), role);

    case MatrixBase::Float:
        return getMatrixValue<MatrixBase::Float, FloatMatrix>(matrix, index.row(), index.column(), role);

    case MatrixBase::Double:
        return getMatrixValue<MatrixBase::Double, DoubleMatrix>(matrix, index.row(), index.column(), role);

    case MatrixBase::ComplexFloat:
        return getMatrixValue<MatrixBase::ComplexFloat, ComplexFloatMatrix>(matrix, index.row(), index.column(), role);

    case MatrixBase::ComplexDouble:
        return getMatrixValue<MatrixBase::ComplexDouble, ComplexDoubleMatrix>(matrix, index.row(), index.column(), role);
    }
}

Qt::ItemFlags MatrixModel::flags(const QModelIndex &index) const
{
    return QAbstractItemModel::flags(index) | Qt::ItemIsEditable;
}

QVariant MatrixModel::data(const QModelIndex & index, int role) const
{
    if (role == Qt::DisplayRole || role == Qt::EditRole) {
        if (d_ptr->matrix)
            return getMatrixData(d_ptr->matrix.data(), index, role);
    }
    return QVariant();
}

bool MatrixModel::setData(const QModelIndex &index, const QVariant &value, int role)
{
    Q_UNUSED(role)
    if (!index.isValid())
        return false;

    if (!d_ptr->matrix)
        return false;

    switch (d_ptr->matrix->type()) {
    case MatrixBase::Int:
    {
        IntMatrix* im = dynamic_cast<IntMatrix*>(d_ptr->matrix.data());
        im->setValue(index.row(), index.column(), value.toInt());
    }
        break;

    case MatrixBase::Float:
    {
        FloatMatrix* fm = dynamic_cast<FloatMatrix*>(d_ptr->matrix.data());
        fm->setValue(index.row(), index.column(), value.toFloat());
    }
        break;

    case MatrixBase::Double:
    {
        DoubleMatrix* dm = dynamic_cast<DoubleMatrix*>(d_ptr->matrix.data());
        dm->setValue(index.row(), index.column(), value.toDouble());
    }
        break;

    case MatrixBase::ComplexFloat:
    {
        ComplexFloatMatrix* cfm = dynamic_cast<ComplexFloatMatrix*>(d_ptr->matrix.data());
        ComplexDoubleType val = qvariant_cast<ComplexDoubleType>(value);
        cfm->setValue(index.row(), index.column(), val);
    }
        break;

    case MatrixBase::ComplexDouble:
    {
        ComplexDoubleMatrix* cdm = dynamic_cast<ComplexDoubleMatrix*>(d_ptr->matrix.data());
        cdm->setValue(index.row(), index.column(), qvariant_cast<ComplexDoubleType>(value));
    }
        break;
    }

    return true;
}

QVariant MatrixModel::headerData(int section, Qt::Orientation orientation, int role) const
{
    if (orientation == Qt::Horizontal && role == Qt::TextAlignmentRole)
        return QVariant(Qt::AlignTop | Qt::AlignHCenter);

    if (orientation == Qt::Vertical && role == Qt::TextAlignmentRole)
        return QVariant(Qt::AlignLeft | Qt::AlignVCenter);

    if (section < d_ptr->horizontalHeaderData.length() && orientation == Qt::Horizontal)
        return d_ptr->horizontalHeaderData.at(section);
    if (section < d_ptr->verticalHeaderData.length() && orientation == Qt::Vertical)
        return d_ptr->verticalHeaderData.at(section);

    return QAbstractTableModel::headerData(section, orientation, role);
}

void MatrixModel::setHorizontalHeaderData(const QVector<double>& data)
{
    d_ptr->horizontalHeaderData = data;
}

QVector<double> MatrixModel::horizontalHeaderData() const
{
    return d_ptr->horizontalHeaderData;
}

void MatrixModel::setVerticalHeaderData(const QVector<double>& data)
{
    d_ptr->verticalHeaderData = data;
}

QVector<double> MatrixModel::verticalHeaderData() const
{
    return d_ptr->verticalHeaderData;
}

void MatrixModel::onMatrixValueChanged(int row, int column, const QVariant &oldVal, const QVariant &newVal)
{
    emit layoutChanged();
}

